﻿using System;
using System.Collections.Generic;
using System.Text;

using Weazel.Badger.Vhdl;
using Weazel.Badger.Vhdl.Types;
using Weazel.Badger.Vhdl.Statements;

namespace Weazel.Willows.InterfaceGeneration
{
	public class FifoElementEntity
		: Entity
	{
		public static string DataInPortName { get { return "data_in"; } }
		public static string CanWritePortName { get { return "can_write";  } }
		public static string DoWritePortName { get { return "do_write"; } }

		public static string DataOutPortName { get { return "data_out"; } }
		public static string CanReadPortName { get { return "can_read"; } }
		public static string DoReadPortName { get { return "do_read"; } }

		public static string ClockPortName { get { return "clock"; } }
		public static string ResetPortName { get { return "reset"; } }


		private static readonly string[] registerUpdateProcessContent =
			new string[] {
						"if (reset = '1') then",
            "    data <= (others=>'0');",
            "    full <= '0';",
            "elsif (clock' event and clock = '1') then",
            "    data <= data_wire;",
            "    full <= full_wire;",
            "end if;"
			};

		private static readonly string[] combinatorialProcessContent =
			new string[] {
            "full_wire <= full;",
            "can_read <= '0';",
            "internal_can_write <= '0';",
            "data_out <= (others=>'0');",
            "data_wire <= data;",
            "",   
            "if ((internal_can_write and do_write) = '1') then",
            "    data_wire <= data_in;",
            "else",
            "    data_wire <= data;",
            "end if;",
            "if ((internal_can_write and (do_write and not full)) = '1') then",
            "    data_out <= data_in;",
            "else",
            "    data_out <= data;",
            "end if;",            
            "internal_can_write <= do_read or not full;",          
            "can_read <= do_write or full;",
            "if ((do_read and not do_write) = '1') then",
            "    full_wire <= '0';",
            "elsif ((do_write and not do_read) = '1') then",
            "    full_wire <= '1';",
            "else",
            "    full_wire <= full;",
            "end if;"
		};

		/// <summary>
		/// Bitwidth of the data in this fifo element
		/// </summary>
		private int dataWidth;
		public int DataWidth
		{
			get { return dataWidth; }
		}

		// Ports
		private Port dataInPort;
		public Port DataInPort
		{
			get { return dataInPort; }
		}

		private Port canWritePort;
		public Port CanWritePort
		{
			get { return canWritePort; }
		}

		private Port doWritePort;
		public Port DoWritePort
		{
			get { return doWritePort; }
		}

		private Port dataOutPort;
		public Port DataOutPort
		{
			get { return dataOutPort; }
		}

		private Port canReadPort;
		public Port CanReadPort
		{
			get { return canReadPort; }
		}

		private Port doReadPort;
		public Port DoReadPort
		{
			get { return doReadPort; }
		}

		private Port clockPort;
		private Port resetPort;

		// outport signals
		private Weazel.Badger.Vhdl.Signal canWriteSignal;

		// Signals
		private Weazel.Badger.Vhdl.Signal data;
		private Weazel.Badger.Vhdl.Signal dataWire;

		private Weazel.Badger.Vhdl.Signal full;
		private Weazel.Badger.Vhdl.Signal fullWire;

		private void createPorts()
		{
			dataInPort = new Port(new StdLogicVector(dataWidth), null, DataInPortName, PortDirection.In, 0);
			AddPort(dataInPort);

			canWritePort = new Port(new StdLogic(), null, CanWritePortName, PortDirection.Out, 1);
			AddPort(canWritePort);

			canWriteSignal = new Weazel.Badger.Vhdl.Signal(new StdLogic(), null, "internal_can_write");
			AddSignal(canWriteSignal);
			AddDeclarativeItem(canWriteSignal);

			doWritePort = new Port(new StdLogic(), null, DoWritePortName, PortDirection.In, 2);
			AddPort(doWritePort);

			dataOutPort = new Port(new StdLogicVector(dataWidth), null, DataOutPortName, PortDirection.Out, 3);
			AddPort(dataOutPort);

			canReadPort = new Port(new StdLogic(), null, CanReadPortName, PortDirection.Out, 4);
			AddPort(canReadPort);

			doReadPort = new Port(new StdLogic(), null, DoReadPortName, PortDirection.In, 5);
			AddPort(doReadPort);

			clockPort = new Port(new StdLogic(), null, ClockPortName, PortDirection.In, 6);
			AddPort(clockPort);

			resetPort = new Port(new StdLogic(), null, ResetPortName, PortDirection.In, 7);
			AddPort(resetPort);

			data = new Weazel.Badger.Vhdl.Signal(new StdLogicVector(dataWidth), null, "data");
			AddSignal(data);
			AddDeclarativeItem(data);

			dataWire = new Weazel.Badger.Vhdl.Signal(new StdLogicVector(dataWidth), null, "data_wire");
			AddSignal(dataWire);
			AddDeclarativeItem(dataWire);

			full = new Weazel.Badger.Vhdl.Signal(new StdLogic(), null, "full");
			AddSignal(full);
			AddDeclarativeItem(full);

			fullWire = new Weazel.Badger.Vhdl.Signal(new StdLogic(), null, "full_wire");
			AddSignal(fullWire);
			AddDeclarativeItem(fullWire);
		}

		private void createProcesses()
		{
			// create combinatorial process
			Process combinatorial = new Process("combinatorial", this);
			combinatorial.Statements.Add(new BlackBoxStatement(combinatorialProcessContent));

			combinatorial.AddSignalToSensivitityList(canWriteSignal);
			combinatorial.AddSignalToSensivitityList(data);
			combinatorial.AddSignalToSensivitityList(full);
			combinatorial.AddSignalToSensivitityList(doWritePort);
			combinatorial.AddSignalToSensivitityList(dataInPort);
			combinatorial.AddSignalToSensivitityList(doReadPort);

			Processes.Add(combinatorial);

			// create register update process
			Process registerUpdate = new Process("register_update", this);
			registerUpdate.Statements.Add(new BlackBoxStatement(registerUpdateProcessContent));

			registerUpdate.AddSignalToSensivitityList(clockPort);
			registerUpdate.AddSignalToSensivitityList(resetPort);

			Processes.Add(registerUpdate);

			Statements.Add(new BlackBoxStatement(string.Format("{0} <= internal_can_write;", CanWritePortName)));
		}

		public FifoElementEntity(int dataWidth)
			: base("fifo_" + dataWidth)
		{
			this.dataWidth = dataWidth;

			createPorts();
			createProcesses();
		}

		public static DesignFile CreateFifoElementDesignFile(int dataWidth)
		{
			FifoElementEntity fifo = new FifoElementEntity(dataWidth);
			DesignFile file = new DesignFile(fifo.Name);
			file.Entities.Add(fifo);

			// add necessary libraries
			file.DesignUnit.Add("library ieee;");
			file.DesignUnit.Add("use IEEE.std_logic_1164.all;");
			file.DesignUnit.Add("use IEEE.std_logic_arith.all;");
			file.DesignUnit.Add("library work;");

			return file;
		}
	}
}
